import { v4 as uuidv4 } from 'uuid';

/**
 * Test isolation utilities
 */
export class TestIsolation {
  private static testRunId: string = uuidv4().substring(0, 8);
  private static testCounter: number = 0;

  /**
   * Generate a unique test ID for the current test
   */
  static generateTestId(testName?: string): string {
    this.testCounter++;
    const timestamp = Date.now().toString(36);
    const counter = this.testCounter.toString(36);
    const random = Math.random().toString(36).substring(2, 6);
    
    if (testName) {
      const sanitizedName = testName.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase();
      return `${sanitizedName}_${this.testRunId}_${timestamp}_${counter}_${random}`;
    }
    
    return `test_${this.testRunId}_${timestamp}_${counter}_${random}`;
  }

  /**
   * Generate unique queue name for test
   */
  static generateQueueName(baseName: string, testId?: string): string {
    const id = testId || this.generateTestId();
    return `${baseName}_${id}`;
  }

  /**
   * Generate unique exchange name for test
   */
  static generateExchangeName(baseName: string, testId?: string): string {
    const id = testId || this.generateTestId();
    return `${baseName}_${id}`;
  }

  /**
   * Generate unique routing key for test
   */
  static generateRoutingKey(baseName: string, testId?: string): string {
    const id = testId || this.generateTestId();
    return `${baseName}.${id}`;
  }

  /**
   * Create isolated test context
   */
  static createTestContext(testName?: string) {
    const testId = this.generateTestId(testName);
    
    return {
      testId,
      queueName: (baseName: string) => this.generateQueueName(baseName, testId),
      exchangeName: (baseName: string) => this.generateExchangeName(baseName, testId),
      routingKey: (baseName: string) => this.generateRoutingKey(baseName, testId),
      cleanup: [] as (() => Promise<void>)[],
      
      addCleanup: function(cleanupFn: () => Promise<void>) {
        this.cleanup.push(cleanupFn);
      },
      
      runCleanup: async function() {
        for (const cleanupFn of this.cleanup.reverse()) {
          try {
            await cleanupFn();
          } catch (error) {
            console.warn(`Cleanup failed for test ${testId}:`, error);
          }
        }
        this.cleanup.length = 0;
      }
    };
  }

  /**
   * Reset test counter (useful for test suites)
   */
  static resetCounter(): void {
    this.testCounter = 0;
  }

  /**
   * Get current test run ID
   */
  static getTestRunId(): string {
    return this.testRunId;
  }
}

/**
 * Test timeout utilities
 */
export class TestTimeouts {
  static readonly UNIT_TEST = 5000;        // 5 seconds for unit tests
  static readonly INTEGRATION_TEST = 15000; // 15 seconds for integration tests
  static readonly E2E_TEST = 30000;        // 30 seconds for e2e tests
  static readonly CONNECTION = 10000;      // 10 seconds for connections
  static readonly MESSAGE_PROCESSING = 5000; // 5 seconds for message processing
  static readonly CLEANUP = 5000;         // 5 seconds for cleanup

  /**
   * Get timeout based on test type
   */
  static getTimeout(testType: 'unit' | 'integration' | 'e2e' | 'connection' | 'message' | 'cleanup'): number {
    switch (testType) {
      case 'unit': return this.UNIT_TEST;
      case 'integration': return this.INTEGRATION_TEST;
      case 'e2e': return this.E2E_TEST;
      case 'connection': return this.CONNECTION;
      case 'message': return this.MESSAGE_PROCESSING;
      case 'cleanup': return this.CLEANUP;
      default: return this.UNIT_TEST;
    }
  }
}

/**
 * Test environment utilities
 */
export class TestEnvironment {
  /**
   * Check if running in CI environment
   */
  static isCI(): boolean {
    return !!(process.env.CI || process.env.CONTINUOUS_INTEGRATION);
  }

  /**
   * Check if integration tests should run
   */
  static shouldRunIntegrationTests(): boolean {
    return process.env.RUN_INTEGRATION_TESTS === 'true' || 
           process.env.NODE_ENV === 'integration';
  }

  /**
   * Check if RabbitMQ is available
   */
  static async isRabbitMQAvailable(): Promise<boolean> {
    try {
      const { isRabbitMQAvailable } = await import('../test-config');
      return await isRabbitMQAvailable();
    } catch (error) {
      return false;
    }
  }

  /**
   * Get test mode (unit, integration, or e2e)
   */
  static getTestMode(): 'unit' | 'integration' | 'e2e' {
    const mode = process.env.TEST_MODE;
    if (mode === 'integration' || mode === 'e2e') {
      return mode;
    }
    return 'unit';
  }
}

/**
 * Conditional test runner
 */
export const describeIf = (condition: boolean | (() => boolean)) => {
  const shouldRun = typeof condition === 'function' ? condition() : condition;
  return shouldRun ? describe : describe.skip;
};

export const itIf = (condition: boolean | (() => boolean)) => {
  const shouldRun = typeof condition === 'function' ? condition() : condition;
  return shouldRun ? it : it.skip;
};

/**
 * Test categories
 */
export const testCategories = {
  unit: describeIf(() => TestEnvironment.getTestMode() === 'unit'),
  integration: describeIf(() => TestEnvironment.shouldRunIntegrationTests()),
  e2e: describeIf(() => TestEnvironment.getTestMode() === 'e2e'),
  withRabbitMQ: describeIf(() => process.env.RABBITMQ_AVAILABLE === 'true'),
};

/**
 * Performance test utilities
 */
export class PerformanceTestUtils {
  /**
   * Measure execution time and memory usage
   */
  static async measurePerformance<T>(
    operation: () => Promise<T>,
    name: string = 'operation'
  ): Promise<{ result: T; duration: number; memoryUsed: number }> {
    const startTime = process.hrtime.bigint();
    const startMemory = process.memoryUsage().heapUsed;
    
    const result = await operation();
    
    const endTime = process.hrtime.bigint();
    const endMemory = process.memoryUsage().heapUsed;
    
    const duration = Number(endTime - startTime) / 1000000; // Convert to milliseconds
    const memoryUsed = endMemory - startMemory;
    
    return { result, duration, memoryUsed };
  }

  /**
   * Assert performance thresholds
   */
  static assertPerformance(
    duration: number,
    memoryUsed: number,
    thresholds: { maxDuration?: number; maxMemory?: number }
  ): void {
    if (thresholds.maxDuration && duration > thresholds.maxDuration) {
      throw new Error(`Performance test failed: duration ${duration}ms exceeds threshold ${thresholds.maxDuration}ms`);
    }
    
    if (thresholds.maxMemory && memoryUsed > thresholds.maxMemory) {
      throw new Error(`Performance test failed: memory usage ${memoryUsed} bytes exceeds threshold ${thresholds.maxMemory} bytes`);
    }
  }
}

/**
 * Retry utilities for flaky tests
 */
export class RetryUtils {
  /**
   * Retry a test operation with exponential backoff
   */
  static async retry<T>(
    operation: () => Promise<T>,
    maxAttempts: number = 3,
    baseDelay: number = 1000
  ): Promise<T> {
    let lastError: Error;
    
    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      try {
        return await operation();
      } catch (error) {
        lastError = error as Error;
        
        if (attempt === maxAttempts) {
          throw lastError;
        }
        
        const delay = baseDelay * Math.pow(2, attempt - 1);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
    
    throw lastError!;
  }
}
